import {assert} from "./utils/debug";
import {getNow, isUndef} from "./utils/lang";
import {BScroll} from "./index";
import {ease} from "./utils/ease";
import {DIRECTION} from "./consts";
import {momentum} from "./utils/momentum";
import {momentumInfo} from "./types";
import {checkPreventDefaultException, tap} from "./utils/dom";

export class CoreMixin {
  constructor(readonly bs: BScroll) {}
  handleStart(event: TouchEvent) {
    this.checkClick(event);
    if (this.bs.base.moving) {
      return;
    }
    if (this.bs.options.preventDefault) {
      event.preventDefault()
    }
    if (this.bs.options.stopPropagation) {
      event.stopPropagation()
    }
    this.bs.base.moving = true;
    this.bs.base.startTime = getNow();
    this.transitionTime();

    this.bs.base.directionX = DIRECTION.static;
    this.bs.base.startX = this.bs.base.x;
    this.bs.base.absStartX = this.bs.base.x;
    this.bs.base.pointX = event.touches[0].clientX; // bs: pageX

    this.bs.base.directionY = DIRECTION.static;
    this.bs.base.startY =  this.bs.base.y;
    this.bs.base.absStartY = this.bs.base.y; // end方法里判断方向用的
    this.bs.base.pointY = event.touches[0].clientY; // bs: pageY

    this.bs.emit('scrollStart');
  }
  handleMove(event: TouchEvent) {
    const { moving, pointX, y, pointY, x, startTime } = this.bs.base;
    const { minScrollY, maxScrollY } = this.bs.rectInfo;
    const { momentumLimitDistance, preventDefault, stopPropagation, preventDefaultException } = this.bs.options;
    const timestamp = getNow();

    if (!moving) {
      return;
    }
    if (preventDefault && !checkPreventDefaultException(<HTMLElement>event.target, preventDefaultException)) {
      event.preventDefault();
    }
    if (stopPropagation) {
      event.stopPropagation();
    }
    const point = event.touches[0];
    // let deltaX = point.clientX - pointX; // -: 左
    let deltaY = point.clientY - pointY; // -: 下
    this.bs.base.pointX = point.clientX; // 不断更新pointY
    this.bs.base.pointY = point.clientY; // 不断更新pointY
    // console.log('deltaY', deltaY);
    deltaY = this.bs.hasVerticalScroll ? deltaY : 0;
    let newY = y + deltaY;
    if (newY > minScrollY || newY < maxScrollY) { // 超过了上 || 下边界
      newY = y + deltaY / 3;
      // newY = newY > minScrollY ? minScrollY : maxScrollY; 不回弹
    }
    this.translate(newY);
    // const dt = timestamp - startTime;
    // console.log(dt);
    /*if (timestamp - startTime > this.bs.options.momentumLimitTime) {
      this.bs.base.startTime = timestamp;
      this.bs.base.startX = x;
      this.bs.base.startY = y;
    }*/

    let scrollLeft = document.documentElement.scrollLeft || window.pageXOffset || document.body.scrollLeft;
    let scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;

    const pX = this.bs.base.pointX - scrollLeft;
    const pY = this.bs.base.pointY - scrollTop;

    // 超出边界
    if (pX > document.documentElement.clientWidth - momentumLimitDistance || pX < momentumLimitDistance || pY < momentumLimitDistance || pY > document.documentElement.clientHeight - momentumLimitDistance) {
      this.handleEnd(event);
    }
  }

  handleEnd(event: TouchEvent) {
    const { startTime, startY, y, absStartY } = this.bs.base;
    const { wrapperHeight, minScrollY, maxScrollY } = this.bs.rectInfo;
    const { bounceTime, flickLimitTime, flickLimitDistance, momentumLimitTime, momentumLimitDistance, preventDefault, stopPropagation } = this.bs.options;

    if (preventDefault) {
      event.preventDefault();
    }
    if (stopPropagation) {
      event.stopPropagation();
    }

    this.bs.base.endTime = getNow();
    // let newX = Math.round(this.x);
    let newY = Math.round(y);

    this.bs.base.moving = false;

    /*if (this.checkClick(event)) {
      this.bs.emit('scrollCancel');
      return;
    }*/

    // 超过边界的回弹
    if (this.resetPosition(bounceTime)) {
      return
    }
    // this.translate(newY);
    const duration = this.bs.base.endTime - startTime;

    // let absDistX = Math.abs(newX - this.startX)
    let absDistY = Math.abs(newY - startY);

    // 轻拂
    if (this.bs.events.get('flick') && duration < flickLimitTime && absDistY < flickLimitDistance) {
      this.bs.emit('flick');
      return;
    }


    // let deltaX = newX - this.absStartX
    const deltaY = newY - absStartY;

    // this.directionX = deltaX > 0 ? DIRECTION_RIGHT : deltaX < 0 ? DIRECTION_LEFT : 0
    this.bs.base.directionY = deltaY > 0 ? DIRECTION.down : deltaY < 0 ? DIRECTION.up : DIRECTION.static;

    let time = 0;
    if (this.bs.options.momentum && duration < momentumLimitTime && (absDistY > momentumLimitDistance)) {
      // 开启 moment 动画
      // const wrapperWidth = ((this.directionX === DIRECTION_RIGHT && left) || (this.directionX === DIRECTION_LEFT && right)) ? this.wrapperWidth : 0;
      const wrapHeight = (this.bs.base.directionY === DIRECTION.down || this.bs.base.directionY === DIRECTION.up) ? wrapperHeight : 0;
      // const momentumX = this.hasHorizontalScroll ? momentum(this.x, this.startX, duration, this.maxScrollX, this.minScrollX, wrapperWidth, this.options, this)
      //   : {destination: newX, duration: 0}
      const momentumY: momentumInfo = this.bs.hasVerticalScroll
        ? momentum(y, startY, duration, maxScrollY, minScrollY, wrapHeight, this.bs.options)
        : { destination: newY, duration: 0, needReset: false };
      // newX = momentumX.destination
      newY = momentumY.destination;
      // time = Math.max(momentumX.duration, momentumY.duration)
      time = momentumY.duration;
      // this.bs.base.inTransition = true;
    }

    let easing = ease.swipe;
    if (newY === y) {
      this.bs.emit('scrollEnd', {
        x: this.bs.base.x,
        y: this.bs.base.y
      });
    } else {
      // change easing 当 moment 动画到达边界
      if (newY > minScrollY || newY < maxScrollY) {
        easing = ease.swipeBounce;
      }
      this.scrollTo(0, newY, time, easing);
    }
  }
  handleTransitionEnd(event: TransitionEvent) {
    if (event.target !== this.bs.scroller || !this.bs.base.inTransition) {
      return
    }
    if (!this.resetPosition(this.bs.options.bounceTime)) {
      this.bs.base.inTransition = false;
      this.transitionTime();
      this.bs.emit('scrollEnd', {
        x: this.bs.base.x,
        y: this.bs.base.y
      });
    }
  }

  private checkClick(event: TouchEvent): boolean {
    // debugger;
    // when in the process of pulling down, it should not prevent click
    let preventClick = this.bs.base.inTransition;

    // we scrolled less than 15 pixels
    if (!this.bs.base.moving) {
      if (!preventClick) {
        if (typeof this.bs.options.tap === 'string') {
          tap(event, this.bs.options.tap);
        }
        /*if (this.options.click && !preventDefaultException(e.target, this.options.preventDefaultException)) {
          click(e)
        }*/
        return true;
      }
      return false;
    }
    return false;
  }

  private translate(y: number, scale = 1) {
    assert(!isUndef(y), 'Translate y is null or undefined.');

    // TODO: 加浏览器前缀
    this.bs.scrollerStyle!.transform = `translate(0, ${y}px) scale(${scale})`;
    this.bs.base.y = y;
    this.bs.emit('scroll', {
      x: this.bs.base.x,
      y: this.bs.base.y
    });
  }
  private resetPosition(time = 0, easeing = ease.bounce): boolean {
    // debugger;
    const { y } = this.bs.base;
    let newX = 0;
    let newY = y;
    const { minScrollY, maxScrollY } = this.bs.rectInfo;
    let roundY = Math.round(y);
    if (!this.bs.hasVerticalScroll || roundY > minScrollY) {
      newY = minScrollY;
    } else if (roundY < maxScrollY) {
      newY = maxScrollY;
    }

    if (newY === y) {
      return false;
    }
    this.scrollTo(newX, newY, time, easeing);
    return true;
  }

  scrollTo(x: number, y: number, time = 0, easing = ease.bounce, isSilent = false) {
    this.bs.base.inTransition = time > 0 && this.bs.base.y !== y;
    this.transitionTimingFunction(easing.style);
    this.transitionTime(time);
    this.translate(y);
  }

  private transitionTimingFunction(easing: string) {
    this.bs.scrollerStyle!.transitionTimingFunction = easing;
  }
  private transitionTime(time = 0) {
    this.bs.scrollerStyle!.transitionDuration = time + 'ms';
  }
}
